/**
 * 滑动窗口实现
 * 用于统计一段时间内某个键的访问次数
 */
class SlidingWindow {
  /**
   * 构造函数
   * @param {number} duration - 持续时间(秒)
   * @param {number} threshold - 阈值，超过该值判定为热点
   */
  constructor(duration, threshold) {
    // 超过10分钟的按10分钟计算
    if (duration > 600) {
      duration = 600;
    }
    
    // 为短时间窗口和长时间窗口设置不同的时间片配置
    if (duration <= 5) {
      this.windowSize = 5;
      this.timeMillisPerSlice = duration * 200;
    } else {
      this.windowSize = 10;
      this.timeMillisPerSlice = duration * 100;
    }
    
    this.threshold = threshold;
    // 保证存储在至少两个window
    this.timeSliceSize = this.windowSize * 2;
    
    // 初始化时间片数组
    this.reset();
  }
  
  /**
   * 自定义构造函数
   * @param {number} timeMillisPerSlice - 每个时间片的毫秒数
   * @param {number} windowSize - 窗口大小
   * @param {number} threshold - 阈值
   * @returns {SlidingWindow} - 滑动窗口实例
   */
  static withCustomConfig(timeMillisPerSlice, windowSize, threshold) {
    const window = new SlidingWindow(0, threshold);
    window.timeMillisPerSlice = timeMillisPerSlice;
    window.windowSize = windowSize;
    window.timeSliceSize = windowSize * 2;
    window.reset();
    return window;
  }
  
  /**
   * 初始化/重置窗口
   */
  reset() {
    this.beginTimestamp = Date.now();
    this.lastAddTimestamp = this.beginTimestamp;
    
    // 初始化时间片数组（在Node.js中，我们不需要AtomicLong，普通数字即可）
    this.timeSlices = new Array(this.timeSliceSize).fill(0);
  }
  
  /**
   * 计算当前所在的时间片的位置
   * @returns {number} - 当前时间片索引
   */
  locationIndex() {
    const now = Date.now();
    
    // 如果当前的key已经超出一整个时间片了，那么就直接初始化
    if (now - this.lastAddTimestamp > this.timeMillisPerSlice * this.windowSize) {
      this.reset();
    }
    
    let index = Math.floor(((now - this.beginTimestamp) / this.timeMillisPerSlice) % this.timeSliceSize);
    if (index < 0) {
      return 0;
    }
    return index;
  }
  
  /**
   * 清除从index开始的旧时间片数据
   * @param {number} index - 当前索引
   */
  clearFromIndex(index) {
    for (let i = 1; i <= this.windowSize; i++) {
      let j = index + i;
      if (j >= this.windowSize * 2) {
        j -= this.windowSize * 2;
      }
      this.timeSlices[j] = 0;
    }
  }
  
  /**
   * 增加count个数量，并判断是否超过阈值
   * @param {number} count - 增加的数量
   * @returns {boolean} - 是否超过阈值
   */
  addCount(count) {
    // 在Node.js单线程环境下，这个方法不需要synchronized关键字
    // 因为JavaScript本身就是单线程执行的
    
    // 当前自己所在的位置，是哪个小时间窗
    const index = this.locationIndex();
    
    // 清空旧的时间片数据
    this.clearFromIndex(index);
    
    // 在当前时间片里增加计数
    this.timeSlices[index] += count;
    
    // 计算窗口内的总和
    let sum = 0;
    sum += this.timeSlices[index];
    
    // 加上前面几个时间片
    for (let i = 1; i < this.windowSize; i++) {
      const prevIndex = (index - i + this.timeSliceSize) % this.timeSliceSize;
      sum += this.timeSlices[prevIndex];
    }
    
    this.lastAddTimestamp = Date.now();
    
    // 判断是否超过阈值
    return sum >= this.threshold;
  }
  
  /**
   * 返回窗口内容的字符串表示
   */
  toString() {
    return {
      windowSize: this.windowSize,
      threshold: this.threshold,
      timeSlices: this.timeSlices,
      beginTimestamp: this.beginTimestamp,
      lastAddTimestamp: this.lastAddTimestamp
    };
  }
}

module.exports = SlidingWindow;
